﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace DisplayDemo
{
    record struct InternalFrame(int Session, ImageData Image, object FrameData, int MinImageDataTime);

    public class FrameDispatcher
    {
        readonly MessageQueue<(ImageData, object)> queue = new();
        readonly Channel<InternalFrame> buffer = Channel.CreateBounded<InternalFrame>(new BoundedChannelOptions(10)
        {
            FullMode = BoundedChannelFullMode.DropOldest
        });

        volatile int session;

        public ISubscription<(ImageData, object)> Frames => queue.Subscribe();

        public FrameDispatcher()
        {
            _ = DispatchBuffer();
        }

        public void Dispatch(ImageData img, int minImageDataTime = 0)
        {
            Dispatch(img, null, minImageDataTime);
        }

        public void Dispatch(ImageData img, object frameData, int minImageDataTime)
        {
            if (minImageDataTime == 0)
            {
                queue.Send((img, frameData));
                return;
            }

            buffer.Writer.TryWrite(new InternalFrame(session, img, frameData, minImageDataTime));
        }

        async Task DispatchBuffer()
        {
            long lastFrameTime = 0;
            for (; ; )
            {
                var (sess, img, data, time) = await buffer.Reader.ReadAsync().ConfigureAwait(false);
                if (sess != session)
                {
                    continue;
                }

                var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                if (now < lastFrameTime)
                {
                    await Task.Delay((int)(lastFrameTime - now)).ConfigureAwait(false);
                    now = lastFrameTime;
                }

                if (sess != session)
                {
                    continue;
                }

                queue.Send((img, data));
                lastFrameTime = now + time;
            }
        }

        public void DropPendingFrames()
        {
            Interlocked.Increment(ref session);
        }
    }
}
